//
// Created by hjie on 23-8-16.
//

#pragma once

#include <cstring>
#include <string>
#include <vector>
#include <sstream>
#include <memory>
#include <functional>
#include "codec_config.h"
extern "C"
{
#include <libavutil/avutil.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/frame.h>
#include <libavutil/buffer.h>
#include <libavutil/hwcontext.h>
#include <libavfilter/avfilter.h>
#include <libavutil/pixfmt.h>
#include <libavutil/pixdesc.h>
}

class InputFileData;
class OutputFileData;

typedef std::function<bool(InputFileData * input_data, OutputFileData * output_data, int index)> InitEncoderData;

class StreamData
{
public:
    StreamData(int index, AVStream * stream);
    ~StreamData();
public:
    InputFileData  * m_input_file;
    OutputFileData * m_output_file;
    bool is_belong_input;

    AVStream * m_stream_data;
    int m_stream_index;
    double m_pts_time;
};

class InputFileData
{
public:
    InputFileData();
    ~InputFileData();
    bool OpenFile(const std::string & filename);
    bool InitDecoderCtx();
    bool InitDecoderCtxWithHwName(const std::string & hw_name);
    bool InitDecoderCtxWithHwType(AVHWDeviceType hw_device_type);
private:
    bool OpenDecoderCtx(AVMediaType media_type);
    bool OpenDecoderCtx(AVMediaType media_type, AVHWDeviceType hw_device_type);
    bool InitHwDecoder(AVMediaType media_type, enum AVHWDeviceType hw_device_type);
public:
    std::string m_input_filename;
    AVFormatContext * m_format_ctx;
    int m_video_stream_index;
    int m_audio_stream_index;
public:
    AVCodecContext * m_video_decoder_ctx;
    AVCodecContext * m_audio_decoder_ctx;
    AVBufferRef    * m_hw_buffer_ref;
    AVPixelFormat    m_hw_pixel_fmt;
public:
    AVPacket * m_read_packet;
    AVFrame  * m_decoder_frame;
public:
    int              m_hw_config_method{0};
    bool             m_get_format_init_status{false};
    OutputFileData * m_output_data;
public:
    bool m_is_eof{false};
};

class OutputFileData
{
public:
    OutputFileData();
    ~OutputFileData();
    bool InitOutputFmtCtx(const std::string & url, AVBufferRef * buffer);
    bool CreateVideoNewStream(InputFileData * input_data, const std::string & hw_name, CodecConfig * codec_config = nullptr);
    bool CreateAudioNewStream(InputFileData * input_data, const std::string & hw_name = "", CodecConfig * codec_config = nullptr);
    bool OpenOutputFile();
    bool WriterHeader();
private:
    const AVCodec * GetAvCodecById(AVCodecID id, AVHWDeviceType device_type = AV_HWDEVICE_TYPE_NONE);
    void VideoCtxOperateCodecConfig(CodecConfig codec_config);
    void AudioCtxOperateCodecConfig(CodecConfig codec_config);
public:
    std::string m_output_url;
    AVFormatContext * m_output_fmt_ctx;
    int m_video_stream_index;
    int m_audio_stream_index;
public:
    // codec and codec ctx
    const AVCodec  * m_video_codec_for_hw{nullptr};

    AVCodecContext * m_video_encoder_ctx;
    AVCodecContext * m_audio_encoder_ctx;
public:
    AVPixelFormat    m_hw_pixel_fmt;        // hw support format
    AVBufferRef *    m_hw_buffer_ref;       // hw buffer reference
    bool             m_init_video_hw{false};// 
    bool             m_get_format_init_status{false};
    int              m_hw_config_method{0};
public:
    AVPacket * m_recv_packet;
    AVFrame  * m_encoder_frame;
};


//;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


class InputFormatContext;
class OutputFormatContext;
class DecoderContext;
class EncoderContext;
class MediaStream;

class InputFormatContext
{
public:
    InputFormatContext();
    ~InputFormatContext();
public:
    bool OpenUrlWithFindStreamInfo(const std::string & file_or_stream);
    bool CreateVideoDecoderCtx(const std::string & codec_name, const CodecConfig & param_config);
    bool CreateAudioDecoderCtx(const std::string & codec_name, const CodecConfig & param_config);
    void HwFrameCtxCallback(std::function<bool()> && callback_func);
public:
    AVFormatContext * AvFormatContext();
    std::shared_ptr<DecoderContext> & VideoDecoder();
    std::shared_ptr<DecoderContext> & AudioDecoder();
    std::shared_ptr<MediaStream>     VideoMediaStream();
    std::shared_ptr<MediaStream>     AudioMediaStream();
    std::shared_ptr<MediaStream>     MediaStreamIndex(int index);
private:
    void ProcessMediaStream();
    bool ProcessHwFrame();
private:
    AVFormatContext * m_format_ctx;
    std::map<int, std::shared_ptr<MediaStream>> m_media_stream_map;
private:
    std::shared_ptr<DecoderContext> m_video_decoder_ctx;
    std::shared_ptr<DecoderContext> m_audio_decoder_ctx;
private:
    std::function<bool()> m_hw_frame_callback;
};

class OutputFormatContext
{
public:
    OutputFormatContext();
    ~OutputFormatContext();
    bool CreateUrl(const std::string & file_or_stream);
    void AddMetaDataToContext(std::map<std::string, std::string> & key_value);
    bool CreateVideoStream(std::shared_ptr<MediaStream> media_stream, const CodecConfig & param_config);
    bool CreateAudioStream(std::shared_ptr<MediaStream> media_stream, const CodecConfig & param_config);

    bool CreateVideoEncoderCtx(std::shared_ptr<MediaStream> media_stream, const std::string & codec_name, const CodecConfig & param_config);
    bool CreateAudioEncoderCtx(std::shared_ptr<MediaStream> media_stream, const std::string & codec_name, const CodecConfig & param_config);

    bool WriterHeaderTailer();
    bool CreateVideoStreamAndInitHwFrameCtx(std::shared_ptr<MediaStream> media_stream);
    bool CreateVideoStreamAndInitHwCtx(AVFilterContext * filter_context);
public:
    AVFormatContext * AvFormatContext();
    std::shared_ptr<EncoderContext> & VideoEncoder();
    std::shared_ptr<EncoderContext> & AudioEncoder();
    std::shared_ptr<MediaStream>     VideoMediaStream();
    std::shared_ptr<MediaStream>     AudioMediaStream();
    std::shared_ptr<MediaStream>     MediaStreamIndex(int index);
private:
    AVFormatContext * m_format_ctx;
    std::map<int, std::shared_ptr<MediaStream>> m_media_stream_map;
private:
    std::shared_ptr<EncoderContext> m_video_encoder_ctx;
    std::shared_ptr<EncoderContext> m_audio_encoder_ctx;
};







